<?php
error_reporting(E_ALL ^ E_NOTICE);

define('ABC_CRITICAL',      0);
define('ABC_ERROR',         1);
define('ABC_ALERT',         2);
define('ABC_WARNING',       3);
define('ABC_NOTICE',        4);
define('ABC_INFO',          5);
define('ABC_DEBUG',         6);
define('ABC_TRACE',         7);
define('ABC_VAR_DUMP',      8);
define('ABC_NO_LOG',      -1);

$php_version = split( "\.", phpversion() );

if( $php_version[0] == 4 && $php_version[1] <= 1 ) {
    if( !function_exists('var_export') ) {
        function var_export( $exp, $ret ) {
            ob_start();
            var_dump( $exp );
            $result = ob_get_contents();
            ob_end_clean();
            return $result;
        }
    }
}

function print_bt() {
    print "<code>\n";
    $cs = debug_backtrace();
    for( $i = 1; $i < count($cs) ; $i++ ) {
        $item = $cs[ $i ];

        for( $j = 0; $j < count($item['args']); $j++ )
            if( is_string($item['args'][$j]) )
                $item['args'][$j] = "\"" . $item['args'][$j] . "\"";
        $args = join(",", $item['args'] );

        if( isset( $item['class'] ) )
            $str = sprintf("%s(%d): %s%s%s(%s)",
                    $item['file'],
                    $item['line'],
                    $item['class'],
                    $item['type'],
                    $item['function'],
                    $args );
        else
            $str = sprintf("%s(%d): %s(%s)",
                    $item['file'],
                    $item['line'],
                    $item['function'],
                    $args );
        echo $str . "<br>\n";
    }
    print "</code>\n";
}

function _die( $str ) {
    print "Выполнение скрипта остановлено по причине: $str<br>\n";
    print_bt();
    exit();
}

class DebugOut {

    var $priorities = array(ABC_CRITICAL    => 'Критический',
            ABC_ERROR       => 'Ошибка',
            ABC_ALERT       => 'Предупреждение',
            ABC_WARNING     => 'Внимание',
            ABC_NOTICE      => 'Уведомление',
            ABC_INFO        => 'Информация',
            ABC_DEBUG       => 'Отладка',
            ABC_TRACE       => 'Трассировка',
            ABC_VAR_DUMP        => 'Дамп'
    );
    var $_ready = false;

    var $_currentPriority = ABC_DEBUG;

    var $_consumers = array();

    var  $_filename;
    var  $_fp;
    var  $_logger_name;


    function DebugOut($name, $logger_name, $level ) {
        $this->_filename = $name;
        $this->_currentPriority = $level;
        $this->_logger_name = $logger_name;
        if ($level > ABC_NO_LOG) {
            $this->_openfile();
        }

        /*Регистрация деструктора*/
        register_shutdown_function(array($this,"close"));
    }



    function log($message, $priority = ABC_INFO) {
        // Прерывает обработку если $priority выше максимального уровня.
        if ($priority > $this->_currentPriority) {
            return false;
        }
        // Добавьте к массиву loglines
        return $this->_writeLine($message, $priority, strftime('%b %d %H:%M:%S'));
    }

    function dump($variable,$name) {
        $priority = ABC_VAR_DUMP;
        if ($priority > $this->_currentPriority ) {
            return false;
        }
        $time = strftime('%b %d %H:%M:%S');
        $message = var_export($variable,true);
        return fwrite($this->_fp,
                sprintf("%s %s [%s] variable %s = %s \r\n",
                $time,
                $this->_logger_name,
                $this->priorities[$priority],
                $name,
                $message)
        );
    }

    function info($message) {
        return $this->log($message, ABC_INFO);
    }

    function debug($message) {
        return $this->log($message, ABC_DEBUG);
    }

    function notice($message) {
        return $this->log($message, ABC_NOTICE);
    }

    function warning($message) {
        return $this->log($message, ABC_WARNING);
    }

    function trace($message) {
        return $this->log($message, ABC_TRACE);
    }

    function error($message) {
        return $this->log($message, ABC_ERROR);
    }



    /**
     * Пишет линию в  logfile
     *
     * @param  string $line      Линия, чтобы писать
     * @param  integer $priority Приоритет этой линии / сообщения
     * @return integer           НЧисло записанных байтов или -1 если ошибка
     * @access private
     */
    function _writeLine($message, $priority, $time) {
        if( fwrite($this->_fp, sprintf("%s %s [%s] %s\r\n", $time, $this->_logger_name, $this->priorities[$priority], $message)) ) {
            return fflush($this->_fp);
        } else {
            return false;
        }
    }

    function _openfile() {
        if (($this->_fp = @fopen($this->_filename, 'a')) == false) {
            return false;
        }
        return true;
    }

    function close() {
        if($this->_currentPriority != ABC_NO_LOG) {
            $this->info("Logger остановлен");
            return fclose($this->_fp);
        }
    }

    /*
  * Организаторские Функции.
  *
    */

    function Factory($name, $logger_name, $level) {
        $instance = new DebugOut($name, $logger_name, $level);
        return $instance;
    }


    function &getWriterSingleton($name, $logger_name, $level = ABC_DEBUG) {

        static $instances;
        if (!isset($instances)) {
            $instances = array();
        }
        $signature = serialize(array($name, $level));

        if (!isset($instances[$signature])) {
            $instances[$signature] = DebugOut::Factory($name, $logger_name, $level);
        }

        return $instances[$signature];
    }


    function attach(&$logObserver) {
        if (!is_object($logObserver)) {
            return false;
        }

        $logObserver->_listenerID = uniqid(rand());

        $this->_listeners[$logObserver->_listenerID] = &$logObserver;
    }

}

define ('ABC_BAD_DATE', -1);

class ExcelDateUtil {


    /*
 * Возвращение 1900 года как целое число TIMESTAMP.
 * используется для UNIX 
 *
    */

    function xls2tstamp($date) {
        $date=$date>25568?$date:25569;
        /*Существовала ошибка при Преобразовании даты меньшей чем 1-1-1970 (tstamp 0) */
        $ofs=(70 * 365 + 17+2) * 86400;
        return ($date * 86400) - $ofs;
    }

    function getDateArray($xls_date) {
        $ret = array();

        // Ошибка высокосного года
        if ($xls_date == 60) {

            $ret['day']   = 29;
            $ret['month'] = 2;
            $ret['year']  = 1900;
            return $ret;

        } else if ($xls_date < 60) {
            // 29-02-1900 ошибка
            $xls_date++;
        }

        // Изменения к Юлианскому  DMY вычислению с дополнением 2415019
        $l = $xls_date + 68569 + 2415019;
        $n = (int)(( 4 * $l ) / 146097);
        $l = $l - (int)(( 146097 * $n + 3 ) / 4);
        $i = (int)(( 4000 * ( $l + 1 ) ) / 1461001);
        $l = $l - (int)(( 1461 * $i ) / 4) + 31;
        $j = (int)(( 80 * $l ) / 2447);
        $ret['day'] = $l - (int)(( 2447 * $j ) / 80);
        $l = (int)($j / 11);
        $ret['month'] = $j + 2 - ( 12 * $l );
        $ret['year'] = 100 * ( $n - 49 ) + $i + $l;

        return $ret;
    }



    function isInternalDateFormat($format) {
        $retval =false;

        switch(format) {
            // Внутренние Форматы Даты как описано на странице 427 в
            // Microsoft Excel Dev's Kit...
            case 0x0e:
            case 0x0f:
            case 0x10:
            case 0x11:
            case 0x12:
            case 0x13:
            case 0x14:
            case 0x15:
            case 0x16:
            case 0x2d:
            case 0x2e:
            case 0x2f:
            // Дополнительные внутренние форматы даты, найденные при
            // использовании Excel v. X 10.1.0 (Mac)
            case 0xa4:
            case 0xa5:
            case 0xa6:
            case 0xa7:
            case 0xa8:
            case 0xa9:
            case 0xaa:
            case 0xab:
            case 0xac:
            case 0xad:
                $retval = true;
                break;
            default: $retval = false;
                break;
        }
        return $retval;
    }

}
define('EXCEL_FONT_RID',0x31);

define('XF_SCRIPT_NONE',0);
define('XF_SCRIPT_SUPERSCRIPT',1);
define('XF_SCRIPT_SUBSCRIPT',2);

define('XF_UNDERLINE_NONE',0x0);
define('XF_UNDERLINE_SINGLE',0x1);
define('XF_UNDERLINE_DOUBLE',0x2);
define('XF_UNDERLINE_SINGLE_ACCOUNTING',0x3);
define('XF_UNDERLINE_DOUBLE_ACCOUNTING',0x4);

define('XF_STYLE_ITALIC', 0x2);
define('XF_STYLE_STRIKEOUT', 0x8);

define('XF_BOLDNESS_REGULAR',0x190);
define('XF_BOLDNESS_BOLD',0x2BC);


class ExcelFont {


    function basicFontRecord() {
        return  array('size'     => 10,
                'script'   => XF_SCRIPT_NONE,
                'undeline' => XF_UNDERLINE_NONE,
                'italic'   => false,
                'strikeout'=> false,
                'bold'     => false,
                'boldness' => XF_BOLDNESS_REGULAR,
                'palete'   => 0,
                'name'     => 'Arial');
    }

    function getFontRecord(&$wb,$ptr) {

        $retval = array('size'     => 0,
                'script'   => XF_SCRIPT_NONE,
                'undeline' => XF_UNDERLINE_NONE,
                'italic'   => false,
                'strikeout'=> false,
                'bold'     => false,
                'boldness' => XF_BOLDNESS_REGULAR,
                'palete'   => 0,
                'name'     => '');
        $retval['size'] = (ord($wb[$ptr])+ 256*ord($wb[$ptr+1]))/20;
        $style=ord($wb[$ptr+2]);
        if (($style & XF_STYLE_ITALIC) != 0) {
            $retval['italic'] = true;
        }
        if (($style & XF_STYLE_STRIKEOUT) != 0) {
            $retval['strikeout'] = true;
        }
        $retval['palete'] = ord($wb[$ptr+4])+256*ord($wb[$ptr+5]);

        $retval['boldness'] = ord($wb[$ptr+6])+256*ord($wb[$ptr+7]);
        $retval['bold'] = $retval['boldness'] == XF_BOLDNESS_REGULAR ? false:true;
        $retval['script'] =  ord($wb[$ptr+8])+256*ord($wb[$ptr+9]);
        $retval['underline'] = ord($wb[$ptr+10]);

        $length = ord($wb[$ptr+14]);
        if($length >0) {
            if(ord($wb[$ptr+15]) == 0) { // Сжатие Unicode
                $retval['name'] = substr($wb,$ptr+16,$length);
            } else { // Без сжатия Unicode
                $retval['name'] = ExcelFont::getUnicodeString($wb,$ptr+15,$length);
            }

        }


        return $retval;
    }

    function toString(&$record,$index) {
        $retval = sprintf("Индекс Шрифта = %d \nРазмер шрифта =%d\nКурсив = %s\nЗачеркнутый=%s\nPalete=%s\nЖирность = %s Полужирный=%s\n Script = %d\n Подчеркивание = %d\n Имя шрифта=%s<hr>",
                $index,
                $record['size'],
                $record['italic']    == true?"true":"false",
                $record['strikeout'] == true?"true":"false",
                $record['palete'],
                $record['boldness'],
                $record['bold'] == true?"true":"false",
                $record['script'],
                $record['underline'],
                $record['name']
        );

        return $retval;

    }


    function getUnicodeString(&$string,$offset,$length) {
        $bstring = "";
        $index   = $offset + 1;   // Начало с младших битов.

        for ($k = 0; $k < $length; $k++) {
            $bstring = $bstring.$string[$index];
            $index        += 2;
        }
        return substr($bstring,0,$length);
    }

    function ExcelToCSS($rec, $app_font=true, $app_size=true, $app_italic=true, $app_bold=true) {
        $ret = "";
        if($app_font==true) {
            $ret = $ret."font-family:".$rec['name']."; ";
        }
        if($app_size==true) {
            $ret = $ret."font-size:".$rec['size']."pt; ";
        }
        if($app_bold==true) {
            if($rec['bold']==true) {
                $ret = $ret."font-weight:bold; ";
            } else {
                $ret = $ret."font-weight:normal; ";
            }
        }
        if($app_italic==true) {
            if($rec['italic']==true) {
                $ret = $ret."font-style:italic; ";
            } else {
                $ret = $ret."font-style:normal; ";
            }
        }

        return $ret;
    }

}
define ( DP_EMPTY, 			0 );
define ( DP_STRING_SOURCE, 	1 );
define ( DP_FILE_SOURCE, 	2 );

class ExcelParserUtil {
    function str2long($str) {
        return ord($str[0]) + 256*(ord($str[1]) +
                        256*(ord($str[2]) + 256*(ord($str[3])) ));
    }
}

class DataProvider {
    function DataProvider( $data, $dataType ) {
        switch( $dataType ) {
            case DP_FILE_SOURCE:
                if( !( $this->_data = @fopen( $data, "rb" )) )
                    return;
                $this->_size = @filesize( $data );
                if( !$this->_size )
                    _die("Невозможно определить размер файла.");
                break;
            case DP_STRING_SOURCE:
                $this->_data = $data;
                $this->_size = strlen( $data );
                break;
            default:
                _die("Обнаружен недопустимый тип данных.");
        }

        $this->_type = $dataType;
        register_shutdown_function( array( $this, "close") );
    }

    function get( $offset, $length ) {
        if( !$this->isValid() )
            _die("В источнике нет данных - пусто.");
        if( $this->_baseOfs + $offset + $length > $this->_size )
            _die("Недопустимое смещение/длина.");

        switch( $this->_type ) {
            case DP_FILE_SOURCE: {
                    if( @fseek( $this->_data, $this->_baseOfs + $offset, SEEK_SET ) == -1 )
                        _die("Неудалось найти позицию в файле указанную в смещении.");
                    return @fread( $this->_data, $length );
                }
            case DP_STRING_SOURCE: {
                    $rc = substr( $this->_data, $this->_baseOfs + $offset, $length );
                    return $rc;
                }
            default:
                _die("Недопустимый тип данных или класс не был инициализирован.");
        }
    }

    function getByte( $offset ) {
        return $this->get( $offset, 1 );
    }

    function getOrd( $offset ) {
        return ord( $this->getByte( $offset ) );
    }

    function getLong( $offset ) {
        $str = $this->get( $offset, 4 );
        return ExcelParserUtil::str2long( $str );
    }

    function getSize() {
        if( !$this->isValid() )
            _die("Источник данных пуст.");
        return $this->_size;
    }

    function getBlocks() {
        if( !$this->isValid() )
            _die("Источник данных пуст.");
        return (int)(($this->_size - 1) / 0x200) - 1;
    }

    function ReadFromFat( $chain, $gran = 0x200 ) {
        $rc = '';
        for( $i = 0; $i < count($chain); $i++ )
            $rc .= $this->get( $chain[$i] * $gran, $gran );
        return $rc;
    }

    function close() {
        switch($this->_type ) {
            case DP_FILE_SOURCE:
                @fclose( $this->_data );
            case DP_STRING_SOURCE:
                $this->_data = null;
            default:
                $_type = DP_EMPTY;
                break;
        }
    }

    function isValid() {
        return $this->_type != DP_EMPTY;
    }

    var $_type = DP_EMPTY;
    var $_data = null;
    var $_size = -1;
    var $_baseOfs = 0;
}

class ExcelFileParser {

    var $dp = null;

    var $max_blocks;
    var $max_sblocks;

    // Внутренние переменные
    var $fat;
    var $sfat;

    // Удаленный: var $sbd;
    //Удаленный: var $syear;

    var $formats;
    var $xf;
    var $fonts;

    var $dbglog;


    function ExcelFileParser($logfile="",$level=ABC_NO_LOG) {
        $this->dbglog = &DebugOut::getWriterSingleton($logfile,"",$level);
        $this->dbglog->info("Logger запущен");
    }

    function populateFormat() {
        $this->dbglog->trace(" populateFormat() function call");

        $ret = array (
                0=> "General",
                1=> "0",
                2=> "0.00",
                3=> "#,##0",
                4=> "#,##0.00",
                5=> "($#,##0_);($#,##0)",
                6=> "($#,##0_);[Red]($#,##0)",
                7=> "($#,##0.00);($#,##0.00)",
                8=> "($#,##0.00_);[Red]($#,##0.00)",
                9=> "0%",
                0xa=> "0.00%",
                0xb=> "0.00E+00",
                0xc=> "# ?/?",
                0xd=> "# ??/??",
                0xe=> "m/d/yy",
                0xf=> "d-mmm-yy",
                0x10=> "d-mmm",
                0x11=> "mmm-yy",
                0x12=> "h:mm AM/PM",
                0x13=> "h:mm:ss AM/PM",
                0x14=> "h:mm",
                0x15=> "h:mm:ss",
                0x16=> "m/d/yy h:mm",

                // 0x17 - 0x24 Зарезервировано
                0x17=> "0x17",
                0x18=> "0x18",
                0x19=> "0x19",
                0x1a=> "0x1a",
                0x1b=> "0x1b",
                0x1c=> "0x1c",
                0x1d=> "0x1d",
                0x1e=> "0x1e",
                0x1f=> "0x1f",
                0x20=> "0x20",
                0x21=> "0x21",
                0x22=> "0x22",
                0x23=> "0x23",
                0x24=> "0x24",

                // 0x17 - 0x24 Зарезервировано
                0x25=> "(#,##0_);(#,##0)",
                0x26=> "(#,##0_);[Red](#,##0)",
                0x27=> "(#,##0.00_);(#,##0.00)",
                0x28=> "(#,##0.00_);[Red](#,##0.00)",
                0x29=> "_(*#,##0_);_(*(#,##0);_(* \"-\"_);_(@_)",
                0x2a=> "_($*#,##0_);_($*(#,##0);_($* \"-\"_);_(@_)",
                0x2b=> "_(*#,##0.00_);_(*(#,##0.00);_(*\"-\"??_);_(@_)",
                0x2c=> "_($*#,##0.00_);_($*(#,##0.00);_($*\"-\"??_);_(@_)",
                0x2d=> "mm:ss",
                0x2e=> "[h]:mm:ss",
                0x2f=> "mm:ss.0",
                0x30=> "##0.0E+0",
                0x31=> "@");

        $this->dbglog->dump($ret,"\$ret");
        $this->dbglog->trace("populateFormat() function return");

        return $ret;

    }

    function xls2tstamp($date) {
        $date=$date>25568?$date:25569;
        /*Раньше существовала ошибка в преобразовании даты 1-1-1970 (tstamp 0)*/
        $ofs=(70 * 365 + 17+2) * 86400;
        return ($date * 86400) - $ofs;
    }


    function getDateArray($date) {
        return ExcelDateUtil::getDateArray($date);
    }

    function isDateFormat($val) {
        $f_i=$this->xf['format'][$val];
        if(preg_match("/[m|d|y]/i",$this->format[$f_i])!=0) {
            if(strrpos($this->format[$f_i],'[')!=FALSE) {
                $tmp = preg_replace("/(\[\/?)(\w+)([^\]]*\])/","'\\1'.''.'\\3'",$this->format[$f_i]);
                if(preg_match("/[m|d|y]/i",$tmp)!=0)
                    return TRUE;
                else
                    return FALSE;
            } else {
                return TRUE;
            }
        } else
            return FALSE;
    }


    function getUnicodeString($str,$ofs) {
        $size=0;
        $i_ofs=0;
        /*	   if (ord($str[$ofs])==255) {
	     $size=ord($str[$ofs])+ 256*(ord($str[$ofs+1]));
	     $i_ofs=2;
	   } else {*/
        $size=ord($str[$ofs]);
        $i_ofs=1;
        /*	   }*/
        return substr($str,$ofs+$i_ofs+1,$size);

    }


    function getByteString($str,$ofs) {
        $size=0;
        $i_ofs=0;
//	   if (ord($str[$ofs])==255) {
//	     $size=ord($str[$ofs])+ 256*(ord($str[$ofs+1]));
//	     $i_ofs=2;
//	   } else {
        $size=ord($str[$ofs]);
        $i_ofs=1;
//	   }
        return substr($str,$ofs+$i_ofs+1,$size);
    }



    /*
	 * Получение цепочек данных
    */
    function get_blocks_chain($start,$small_fat=false) {

        $this->dbglog->trace("get_blocks_chain(".var_export($start,true).",".var_export($small_fat,true).") function call ");
        $chain = array();

        $next_block = $start;
        if( !$small_fat ) {
            while(  ($next_block!=0xfffffffe) &&
                    ($next_block <= $this->max_blocks) &&
                    ($next_block < count($this->fat)) ) {
                $chain[] = $next_block;
                $next_block = $this->fat[$next_block];
            }
        } else {
            while(  ($next_block!=0xfffffffe) &&
                    ($next_block <= $this->max_sblocks) &&
                    ($next_block < count($this->sfat)) ) {
                $chain[] = $next_block;
                $next_block = $this->sfat[$next_block];
            }
        }

        if( $next_block != 0xfffffffe )
            return false;

        $this->dbglog->dump($chain,"\$chain");
        $this->dbglog->trace("get_blocks_chain() function return");
        return $chain;
    }

    /* Поиcк потока по имени
	 *
    */

    function find_stream( $dir, $item_name,$item_num=0) {

        $this->dbglog->trace("find_stream(".var_export($dir,true).",".var_export($item_name,true).",".var_export($item_num,true).") function call ");

        $dt = $dir->getOrd( $item_num * 0x80 + 0x42 );
        $prev = $dir->getLong( $item_num * 0x80 + 0x44 );
        $next = $dir->getLong( $item_num * 0x80 + 0x48 );
        $dir_ = $dir->getLong( $item_num * 0x80 + 0x4c );

        $curr_name = '';
        if( ($dt==2) || ($dt==5) )
            for( $i=0;
            $i < ( $dir->getOrd( $item_num * 0x80 + 0x40 ) +
                            256 * $dir->getOrd( $item_num * 0x80 + 0x41 ) )/2-1;
            $i++ )
                $curr_name .= $dir->getByte( $item_num * 0x80 + $i * 2 );

        if( (($dt==2) || ($dt==5)) && (strcmp($curr_name,$item_name)==0) ) {
            $this->dbglog->trace("find_stream() function return with ".var_export($item_num,true));
            return $item_num;
        }

        if( $prev != 0xffffffff ) {
            $i = $this->find_stream( $dir, $item_name, $prev);
            if( $i>=0 ) {
                $this->dbglog->trace("find_stream() function return with ".var_export($i,true));
                return $i;
            }
        }
        if( $next != 0xffffffff ) {
            $i = $this->find_stream( $dir, $item_name, $next);
            if( $i>=0 ) {
                $this->dbglog->trace("find_stream() function return with ".var_export($i,true));
                return $i;
            }
        }
        if( $dir_ != 0xffffffff ) {
            $i = $this->find_stream( $dir, $item_name, $dir_ );
            if( $i>=0 ) {
                $this->dbglog->trace("find_stream() function return with ".var_export($i,true));
                return $i;
            }
        }
        $this->dbglog->trace("find_stream() function return with -1");
        return -2;
    }

    function rk_decode($rk) {

//	    $this->dbglog->trace("rk_decode(".var_export($rk,true).") function call");
        $res = array();
        if( $rk & 2 ) {
            //целое
            $val = ($rk & 0xfffffffc) >> 2;
            if( $rk & 1 ) $val = $val / 100;
            if (((float)$val) == floor((float)$val)) {
                $res['val'] = (int)$val;
                $res['type'] = 1;
            } else {
                $res['val'] = (float)$val;
                $res['type'] = 2;
            }

        } else {
            //вещественное
            $res['type'] = 2;
            $frk = $rk;

            $fexp =  (($frk & 0x7ff00000) >> 20) - 1023;
            $val = 1+(($frk & 0x000fffff) >> 2)/262144;

            if( $fexp > 0 ) {
                for( $i=0; $i<$fexp; $i++ )
                    $val *= 2;
            } else {
                if( $fexp==-1023 ) {
                    $val=0;
                } else {
                    for( $i=0; $i<abs($fexp); $i++ )
                        $val /= 2;
                }
            }

            if( $rk & 1 ) $val = $val / 100;
            if( $rk & 0x80000000 ) $val = -$val;

            $res['val'] = (float)$val;
        }
//		$this->dbglog->trace("rk_decode() function returns");
        return $res;
    }

    // Анализ рабочих листов
    //-----------------

    function parse_worksheet($ws) {

        $this->dbglog->debug("parse_worksheet(DATA) function");
        if( strlen($ws) <= 0 ) {
            $this->dbglog->trace("parse_worksheet() function returns 7 (Data not Found)");
            return 7;
        }
        if( strlen($ws) <  4 ) {
            $this->dbglog->trace("parse_worksheet() function returns 6 (File Corrupted)");
            return 6;
        }

        //анализ заголовка рабочей книги
        if( strlen($ws) < 256*ord($ws[3])+ord($ws[2]) ) return 6;

        if( ord($ws[0]) != 0x09 ) return 6;
        $vers = ord($ws[1]);
        if( ($vers!=0) && ($vers!=2) && ($vers!=4) && ($vers!=8) )
            return 8;

        if( $vers!=8 ) {
            $biff_ver = ($ver+4)/2;
        } else {
            if( strlen($ws) < 12 ) return 6;
            switch( ord($ws[4])+256*ord($ws[5]) ) {
                case 0x0500:
                    if( ord($ws[0x0a])+256*ord($ws[0x0b]) < 1994 ) {
                        $biff_ver = 5;
                    } else {
                        switch(ord( $ws[8])+256*ord($ws[9]) ) {
                            case 2412:
                            case 3218:
                            case 3321:
                            /*dbg*/ 	            $this->dbglog->debug("Анализатор BIFF версия - 5");
                                $biff_ver = 5;
                                break;
                            default:
                                $this->dbglog->debug("Анализатор BIFF версия 7");
                                $biff_ver = 7;
                                break;
                        }
                    }
                    break;
                case 0x0600:
                /*DBG*/		    $this->dbglog->debug("Анализатор BIFF версия 8");
                    $biff_ver = 8;
                    break;
                default:
                    return 8;
            }
        }

        if( $biff_ver < 5 ) {
            /*DBG*/  $this->dbglog->debug("parse_worksheet() function found ($biff_ver < 5) return 8");
            return 8;
        }
        $ptr = 0;
        $data = array('biff_version' => $biff_ver );

        while( (ord($ws[$ptr])!=0x0a) && ($ptr<strlen($ws)) ) {

            switch (ord($ws[$ptr])+256*ord($ws[$ptr+1])) {


                // Формула
                //Номер

                case 0x0203:
                case 0x0006:
                case 0x0206:
                case 0x0406:

                /*DBG*/     $this->dbglog->trace("found NUMBER");

                    if( ($biff_ver < 3) ) {
                        /*DBG*/         $this->dbglog->trace("$biff_ver < 3 break;");
                        break;
                    }
                    if( (ord($ws[$ptr+2])+256*ord($ws[$ptr+3])) < 14 ) {
                        /*DBG*/         $this->dbglog->debug("parse_worksheet() return 6");
                        return 6;
                    }

                    $row = ord($ws[$ptr+4])+256*ord($ws[$ptr+5]);
                    $col = ord($ws[$ptr+6])+256*ord($ws[$ptr+7]);
                    $num_lo = ExcelParserUtil::str2long(substr($ws,$ptr+10,4));
                    $num_hi = ExcelParserUtil::str2long(substr($ws,$ptr+14,4));
                    $xf_i = ord($ws[$ptr+8])+256*ord($ws[$ptr+9]);

                    if($this->isDateFormat($xf_i)) {
                        $data['cell'][$row][$col]['type'] = 3;
                    } else {
                        $data['cell'][$row][$col]['type'] = 2;
                    }

                    $fonti = $this->xf['font'][$xf_i];
                    $data['cell'][$row][$fc+$i]['font'] = $fonti;

                    $fexp = (($num_hi & 0x7ff00000) >> 20) - 1023;
                    $val = 1+(($num_hi & 0x000fffff)+$num_lo/4294967296)/1048576;

                    if( $fexp > 0 ) {
                        for( $i=0; $i<$fexp; $i++ )
                            $val *= 2;
                    } else {
                        for( $i=0; $i<abs($fexp); $i++ )
                            $val /= 2;
                    }
                    if( $num_hi & 0x80000000 ) $val = -$val;

                    $data['cell'][$row][$col]['data'] = (float)$val;

                    if( !isset($data['max_row']) ||
                            ($data['max_row'] < $row) )
                        $data['max_row'] = $row;

                    if( !isset($data['max_col']) ||
                            ($data['max_col'] < $col) )
                        $data['max_col'] = $col;

                    break;

                // RK
                case 0x027e:
                /*DBG*/  $this->dbglog->trace("found RK");
                    if( ($biff_ver < 3) ) break;
                    if( (ord($ws[$ptr+2])+256*ord($ws[$ptr+3])) < 0x0a )
                        return 6;
                    $row  = ord($ws[$ptr+4])+256*ord($ws[$ptr+5]);
                    $col  = ord($ws[$ptr+6])+256*ord($ws[$ptr+7]);
                    $xf_i = ord($ws[$ptr+8])+256*ord($ws[$ptr+9]);
                    $val  = $this->rk_decode(
                            ExcelParserUtil::str2long(substr($ws,$ptr+10,4))
                    );

                    if($this->isDateFormat($xf_i)==TRUE) {
                        $data['cell'][$row][$col]['type'] = 3;
                    } else {
                        $data['cell'][$row][$col]['type'] = $val['type'];
                    }
                    $fonti = $this->xf['font'][$xf_i];

                    $data['cell'][$row][$col]['font'] = $fonti;
                    $data['cell'][$row][$col]['data'] = $val['val'];


                    if( !isset($data['max_row']) ||
                            ($data['max_row'] < $row) )
                        $data['max_row'] = $row;

                    if( !isset($data['max_col']) ||
                            ($data['max_col'] < $col) )
                        $data['max_col'] = $col;

                    break;

                // MULRK
                case 0x00bd:
                /*DBG*/  $this->dbglog->trace("found  MULL RK");
                    if( ($biff_ver < 5) ) break;
                    $sz = ord($ws[$ptr+2])+256*ord($ws[$ptr+3]);
                    if( $sz < 6 ) return 6;

                    $row = ord($ws[$ptr+4])+256*ord($ws[$ptr+5]);
                    $fc = ord($ws[$ptr+6])+256*ord($ws[$ptr+7]);
                    $lc = ord($ws[$ptr+$sz+2])+256*ord($ws[$ptr+$sz+3]);

                    for( $i=0; $i<=$lc-$fc; $i++) {
                        $val = $this->rk_decode(
                                ExcelParserUtil::str2long(substr($ws,$ptr+10+$i*6,4))
                        );

                        $xf_i=ord($ws[$ptr+8+$i*6])+256*ord($ws[($ptr+9+$i*6)]);
                        if($this->isDateFormat($xf_i)==TRUE) {
                            $data['cell'][$row][$fc+$i]['type'] = 3;
                        } else {
                            $data['cell'][$row][$fc+$i]['type'] = $val['type'];
                        }
                        $fonti = $this->xf['font'][$xf_i];
                        $data['cell'][$row][$fc+$i]['font'] = $fonti;
                        $data['cell'][$row][$fc+$i]['data'] = $val['val'];
                    }

                    if( !isset($data['max_row']) ||
                            ($data['max_row'] < $row) )
                        $data['max_row'] = $row;

                    if( !isset($data['max_col']) ||
                            ($data['max_col'] < $lc) )
                        $data['max_col'] = $lc;

                    break;

                // МЕТКИ
                case 0x0204:
                /*DBG*/  $this->dbglog->trace("found LABEL");
                    if( ($biff_ver < 3) ) {
                        break;
                    }
                    if( (ord($ws[$ptr+2])+256*ord($ws[$ptr+3])) < 8 ) {
                        return 6;
                    }
                    $row = ord($ws[$ptr+4])+256*ord($ws[$ptr+5]);
                    $col = ord($ws[$ptr+6])+256*ord($ws[$ptr+7]);
                    $xf = ord($ws[$ptr+8])+256*ord($ws[$ptr+9]);
                    $fonti = $this->xf['font'][$xf];
                    $font =  $this->fonts[$fonti];


                    $str_len = ord($ws[$ptr+10])+256*ord($ws[$ptr+11]);

                    if( $ptr+12+$str_len > strlen($ws) )
                        return 6;
                    $this->sst['unicode'][] = false;
                    $this->sst['data'][] = substr($ws,$ptr+12,$str_len);
                    $data['cell'][$row][$col]['type'] = 0;
                    $sst_ind = count($this->sst['data'])-1;
                    $data['cell'][$row][$col]['data'] = $sst_ind;
                    $data['cell'][$row][$col]['font'] = $fonti;

                    /*			echo str_replace("\n","<br>\n", ExcelFont::toString($font,$fonti));
		    echo "Шрифт строки записи ".$this->sst['data'][$sst_ind]."<br>";*/

                    if( !isset($data['max_row']) ||
                            ($data['max_row'] < $row) )
                        $data['max_row'] = $row;

                    if( !isset($data['max_col']) ||
                            ($data['max_col'] < $col) )
                        $data['max_col'] = $col;



                    break;

                // Метки ОСТ-SST
                case 0x00fd:
                    if( $biff_ver < 8 ) break;
                    if( (ord($ws[$ptr+2])+256*ord($ws[$ptr+3])) < 0x0a )
                        return 6;
                    $row = ord($ws[$ptr+4])+256*ord($ws[$ptr+5]);
                    $col = ord($ws[$ptr+6])+256*ord($ws[$ptr+7]);
                    $xf = ord($ws[$ptr+8])+256*ord($ws[$ptr+9]);
                    $fonti = $this->xf['font'][$xf];
                    $font = &$this->fonts[$fonti];

                    $data['cell'][$row][$col]['type'] = 0;
                    $sst_ind = ExcelParserUtil::str2long(substr($ws,$ptr+10,4));
                    $data['cell'][$row][$col]['data'] = $sst_ind;
                    $data['cell'][$row][$col]['font'] = $fonti;

                    /*            echo "Шрифт записи для строки  $row,$col<br>";
			echo str_replace("\n","<br>\n", ExcelFont::toString($font,$fonti));*/

                    if( !isset($data['max_row']) ||
                            ($data['max_row'] < $row) )
                        $data['max_row'] = $row;

                    if( !isset($data['max_col']) ||
                            ($data['max_col'] < $col) )
                        $data['max_col'] = $col;

                    break;

                // Неизвестный, неподдерживаемый или неиспользованный код
                default:
                    break;
            }

            $ptr += 4+256*ord($ws[$ptr+3])+ord($ws[$ptr+2]);
        }
        //$this->dbglog->debug("parse_worksheet() function returns ".var_export($data,true));

        return $data;

    }

    // Анализатор рабочей книги
    //----------------

    function parse_workbook( $f_header, $dp ) {

        /*DBG*/ $this->dbglog->debug("parse_workbook() function");

        $root_entry_block = $f_header->getLong(0x30);
        $num_fat_blocks = $f_header->getLong(0x2c);

        /*TRC*/ $this->dbglog->trace("Анализ заголовка");

        $this->fat = array();
        for( $i = 0; $i < $num_fat_blocks; $i++ ) {
            /*TRC*/		$this->dbglog->trace("FOR LOOP iteration i =".$i);

            $fat_block = $f_header->getLong( 0x4c + 4 * $i );
            $fatbuf = $dp->get( $fat_block * 0x200, 0x200 );
            $fat = new DataProvider( $fatbuf, DP_STRING_SOURCE );

            if( $fat->getSize() < 0x200 ) {
                /*DBG*/    		$this->dbglog->debug("parse_workbook() function found (strlen($fat) < 0x200) returns 6");
                return 6;
            }

            for( $j=0; $j<0x80; $j++ )
                $this->fat[] = $fat->getLong( $j * 4 );

            $fat->close();
            unset( $fat_block, $fatbuf, $fat );
        }

        /*DBG*/ $this->dbglog->dump( $this->fat, "\$fat" );

        if( count($this->fat) < $num_fat_blocks ) {
            /*DBG*/    	$this->dbglog->debug("parse_workbook() function found (count($this->fat) < $num_fat_blocks) returns 6");
            return 6;
        }

        $chain = $this->get_blocks_chain($root_entry_block);
        $dir = new DataProvider( $dp->ReadFromFat( $chain ), DP_STRING_SOURCE );
        unset( $chain );

        $this->sfat = array();
        $small_block = $f_header->getLong( 0x3c );
        if( $small_block != 0xfeffffff ) {

            $root_entry_index = $this->find_stream( $dir, 'Root Entry');

            // Удалено для совместимости МАС

            //if( $root_entry_index < 0 ) {
            /*DBG*/    		//$this->dbglog->debug("parse_workbook() function dont found Root Entry returns 6");
            //return 6;
            //}

            $sdc_start_block = $dir->getLong( $root_entry_index * 0x80 + 0x74 );
            $small_data_chain = $this->get_blocks_chain($sdc_start_block);
            $this->max_sblocks = count($small_data_chain) * 8;

            $schain = $this->get_blocks_chain($small_block);
            for( $i = 0; $i < count( $schain ); $i++ ) {

                $sfatbuf = $dp->get( $schain[$i] * 0x200, 0x200 );
                $sfat = new DataProvider( $sfatbuf, DP_STRING_SOURCE );

                //$this->dbglog->dump( strlen($sfatbuf), "strlen(\$sftabuf)");
                //$this->dbglog->dump( $sfat, "\$sfat");

                if( $sfat->getSize() < 0x200 ) {
                    /*DBG*/    	 		$this->dbglog->debug("parse_workbook() function found (strlen($sfat) < 0x200)  returns 6");
                    return 6;
                }

                for( $j=0; $j<0x80; $j++ )
                    $this->sfat[] = $sfat->getLong( $j * 4 );

                $sfat->close();
                unset( $sfatbuf, $sfat );
            }
            unset( $schain );

            $sfcbuf = $dp->ReadFromFat( $small_data_chain );
            $sdp = new DataProvider( $sfcbuf, DP_STRING_SOURCE );
            unset( $sfcbuf, $small_data_chain );
        }

        $workbook_index = $this->find_stream( $dir, 'Workbook' );
        if( $workbook_index<0 ) {
            $workbook_index = $this->find_stream( $dir, 'Book' );
            if( $workbook_index<0 ) {
                /*DBG*/    	    $this->dbglog->debug("parse_workbook() function workbook index not found returns 7");
                return 7;
            }
        }

        $workbook_start_block = $dir->getLong( $workbook_index * 0x80 + 0x74 );
        $workbook_length = $dir->getLong( $workbook_index * 0x80 + 0x78 );
        $wb = '';

        if( $workbook_length > 0 ) {
            if( $workbook_length >= 0x1000 ) {
                $chain = $this->get_blocks_chain($workbook_start_block);
                $wb = $dp->ReadFromFat( $chain );
            } else {
                $chain = $this->get_blocks_chain($workbook_start_block,true);
                $wb = $sdp->ReadFromFat( $chain, 0x40 );
                unset( $sdp );
            }
            $wb = substr($wb,0,$workbook_length);
            if( strlen($wb) != $workbook_length )
                return 6;
            unset( $chain );
        }

        // Unset fat arrays
        unset( $this->fat, $this->sfat );

        if( strlen($wb) <= 0 ) {
            /*DBG*/    $this->dbglog->debug("parse_workbook() function workbook found (strlen($wb) <= 0) returns 7");
            return 7;
        }
        if( strlen($wb) <  4 ) {
            /*DBG*/    $this->dbglog->debug("parse_workbook() function workbook found (strlen($wb) < 4) returns 6");
            return 6;
        }

        //анализатор заголовка книги
        if( strlen($wb) < 256*ord($wb[3])+ord($wb[2]) ) {
            /*DBG*/ 	$this->dbglog->debug("parse_workbook() function workbook found (strlen($wb) < 256*ord($wb[3])+ord($wb[2])) < 4) returns 6");
            return 6;
        }

        if( ord($wb[0]) != 0x09 ) {
            /*DBG*/ 	$this->dbglog->debug("parse_workbook() function workbook found (ord($wb[0]) != 0x09) returns 6");
            return 6;
        }

        $vers = ord($wb[1]);
        if( ($vers!=0) && ($vers!=2) && ($vers!=4) && ($vers!=8) ) {
            return 8;
        }
        if( $vers!=8 )
            $biff_ver = ($ver+4)/2;
        else {

            if( strlen($wb) < 12 ) return 6;
            switch( ord($wb[4])+256*ord($wb[5]) ) {
                case 0x0500:
                    if( ord($wb[0x0a])+256*ord($wb[0x0b]) < 1994 )
                        $biff_ver = 5;
                    else {
                        switch(ord( $wb[8])+256*ord($wb[9]) ) {
                            case 2412:
                            case 3218:
                            case 3321:
                                $biff_ver = 5;
                                break;
                            default:
                                $biff_ver = 7;
                                break;
                        }
                    }
                    break;
                case 0x0600:
                    $biff_ver = 8;
                    break;
                default:
                    return 8;
            }
        }

        if( $biff_ver < 5 ) return 8;

        $ptr = 0;
        $this->worksheet['offset'] = array();
        $this->worksheet['options'] = array();
        $this->worksheet['unicode'] = array();
        $this->worksheet['name'] = array();
        $this->worksheet['data'] = array();
        $this->format = $this->populateFormat();
        $this->fonts = array();
        $this->fonts[0] = ExcelFont::basicFontRecord();

        $this->xf = array();
        $this->xf['format'] = array();
        $this->xf['font'] = array();
        $this->xf['type_prot'] = array();
        $this->xf['alignment'] = array();
        $this->xf['decoration'] = array();

        $xf_cnt=0;

        $this->sst['unicode'] = array();
        $this->sst['data'] = array();

        $opcode = 0;
        $sst_defined = false;
        $wblen = strlen($wb);

        while( (ord($wb[$ptr])!=0x0a) && ($ptr<$wblen) ) {
            $oc = ord($wb[$ptr])+256*ord($wb[$ptr+1]);
            if( $oc != 0x3c )
                $opcode = $oc;

            switch ($opcode) {

                case 0x0085:
                    $ofs = ExcelParserUtil::str2long(substr($wb,$ptr+4,4));
                    $this->worksheet['offset'][] = $ofs;
                    $this->worksheet['options'][] = ord($wb[$ptr+8])+256*ord($wb[$ptr+9]);
                    if( $biff_ver==8 ) {
                        $len = ord($wb[$ptr+10]);
                        if( (ord($wb[$ptr+11]) & 1) > 0 ) {
                            $this->worksheet['unicode'][] = true;
                            $len = $len*2;
                        } else {
                            $this->worksheet['unicode'][] = false;
                        }
                        $this->worksheet['name'][] = substr($wb,$ptr+12,$len);
                    } else {
                        $this->worksheet['unicode'][] = false;
                        $len = ord($wb[$ptr+10]);
                        $this->worksheet['name'][] = substr($wb,$ptr+11,$len);
                    }

                    $pws = $this->parse_worksheet(substr($wb,$ofs));
                    if( is_array($pws) )
                        $this->worksheet['data'][] = $pws;
                    else
                        return $pws;
                    break;

                // Формат
                case 0x041e:
                    $fidx = ord($wb[$ptr+4])+256*ord($wb[$ptr+5]);
                    if($fidx<0x31 ||$fidx==0x31 )
                        break;
                    elseif($biff_ver>7)
                        $this->format[$fidx] = $this->getUnicodeString($wb,$ptr+6);
                    break;

                // Шрифт 0x31
                case EXCEL_FONT_RID:
                    $rec = ExcelFont::getFontRecord($wb,$ptr+4);
                    $this->fonts[count($this->fonts)] = $rec;
                    /*echo str_replace("\n","<br>\n",ExcelFont::toString($rec,count($this->fonts)-1));
echo "Шрифт записи<br>" */;
                    break;

                // XF
                case 0x00e0:
                    $this->xf['font'][$xf_cnt] = ord($wb[$ptr+4])+256*ord($wb[$ptr+5]);
                    $this->xf['format'][$xf_cnt] = ord($wb[$ptr+6])+256*ord($wb[$ptr+7]);
                    $this->xf['type'][$xf_cnt]  = "1";
                    $this->xf['bitmask'][$xf_cnt] = "1";
                    $xf_cnt++;
                    break;

                // SST
                case 0x00fc:
                    if( $biff_ver < 8 ) break;

                    $sbuflen = ord($wb[$ptr+2]) + 256*ord($wb[$ptr+3]);

                    if( $oc != 0x3c ) {
                        if( $sst_defined ) return 6;
                        $snum = ExcelParserUtil::str2long(substr($wb,$ptr+8,4));
                        $sptr = $ptr+12;
                        $sst_defined = true;
                    } else {
                        if( $rslen > $slen ) {
                            $sptr = $ptr+4;
                            $rslen -= $slen;
                            $slen = $rslen;

                            if( (ord($wb[$sptr]) & 1) > 0 ) {
                                if( $char_bytes == 1 ) {
                                    $sstr = '';
                                    for( $i=0; $i<strlen($str); $i++ )
                                        $sstr .= $str[$i].chr(0);
                                    $str = $sstr;
                                    $char_bytes=2;
                                }
                                $schar_bytes = 2;
                            } else {
                                $schar_bytes = 1;
                            }

                            if( $sptr+$slen*$schar_bytes > $ptr+4+$sbuflen )
                                $slen = ($ptr+$sbuflen-$sptr+3)/$schar_bytes;

                            $sstr = substr($wb,$sptr+1,$slen*$schar_bytes);

                            if( ($char_bytes == 2) && ($schar_bytes == 1) ) {
                                $sstr2 = '';
                                for( $i=0; $i<strlen($sstr); $i++ )
                                    $sstr2 .= $sstr[$i].chr(0);
                                $sstr = $sstr2;
                            }
                            $str .= $sstr;

                            $sptr += $slen*$schar_bytes+1+4*$rt+$fesz;
                            if( $slen < $rslen ) {
                                if( ($sptr >= strlen($wb)) ||
                                        ($sptr < $ptr+4+$sbuflen) ||
                                        (ord($wb[$sptr]) != 0x3c) ) {
                                    return 6;
                                }
                                break;
                            } else {
                                if( $char_bytes == 2 ) {
                                    $this->sst['unicode'][] = true;
                                } else {
                                    $this->sst['unicode'][] = false;
                                }
                                $this->sst['data'][] = $str;
                                $snum--;
                            }
                        } else {
                            $sptr = $ptr+4;
                            if( $sptr > $ptr ) $sptr += 4*$rt+$fesz;
                        }
                    }

                    while(  ($sptr < $ptr+4+$sbuflen) &&
                            ($sptr < strlen($wb)) &&
                            ($snum > 0) ) {
                        $rslen = ord($wb[$sptr])+256*ord($wb[$sptr+1]);
                        $slen = $rslen;

                        if( (ord($wb[$sptr+2]) & 1) > 0 ) {
                            $char_bytes = 2;
                        } else {
                            $char_bytes = 1;
                        }

                        $rt = 0;
                        $fesz = 0;
                        switch (ord($wb[$sptr+2]) & 0x0c) {
                            // Rich-Text with Far-East
                            case 0x0c:
                                $rt = ord($wb[$sptr+3])+256*(ord($wb[$sptr+4]));
                                $fesz = ExcelParserUtil::str2long(substr($wb,$sptr+5,4));
                                if( $sptr+9+$slen*$char_bytes > $ptr+4+$sbuflen )
                                    $slen = ($ptr+$sbuflen-$sptr-5)/$char_bytes;
                                $str = substr($wb,$sptr+9,$slen*$char_bytes);
                                $sptr += $slen*$char_bytes+9;
                                break;

                            // Rich-Text
                            case 8:
                                $rt = ord($wb[$sptr+3])+256*(ord($wb[$sptr+4]));
                                if( $sptr+5+$slen*$char_bytes > $ptr+4+$sbuflen )
                                    $slen = ($ptr+$sbuflen-$sptr-1)/$char_bytes;
                                $str = substr($wb,$sptr+5,$slen*$char_bytes);
                                $sptr += $slen*$char_bytes+5;
                                break;

                            // Far-East
                            case 4:
                                $fesz = ExcelParserUtil::str2long(substr($wb,$sptr+3,4));
                                if( $sptr+7+$slen*$char_bytes > $ptr+4+$sbuflen )
                                    $slen = ($ptr+$sbuflen-$sptr-3)/$char_bytes;
                                $str = substr($wb,$sptr+7,$slen*$char_bytes);
                                $sptr += $slen*$char_bytes+7;
                                break;

                            // Сжатый или несжатый unicode
                            case 0:
                                if( $sptr+3+$slen*$char_bytes > $ptr+4+$sbuflen )
                                    $slen = ($ptr+$sbuflen-$sptr+1)/$char_bytes;
                                $str = substr($wb,$sptr+3,$slen*$char_bytes);
                                $sptr += $slen*$char_bytes+3;
                                break;
                        }

                        if( $slen < $rslen ) {
                            if( ($sptr >= strlen($wb)) ||
                                    ($sptr < $ptr+4+$sbuflen) ||
                                    (ord($wb[$sptr]) != 0x3c) ) return 6;
                        } else {
                            if( $char_bytes == 2 ) {
                                $this->sst['unicode'][] = true;
                            } else {
                                $this->sst['unicode'][] = false;
                            }
                            $sptr += 4*$rt+$fesz;
                            $this->sst['data'][] = $str;
                            $snum--;
                        }
                    } // switch
                    break;
            } // switch

            // !!! Оптимизация:
            //  $this->wsb[] = substr($wb,$ptr,4+256*ord($wb[$ptr+3])+ord($wb[$ptr+2]));

            $ptr += 4+256*ord($wb[$ptr+3])+ord($wb[$ptr+2]);
        } // while

        // !!! Оптимизация:
        //  $this->workbook = $wb;
        $this->biff_version = $biff_ver;
        /*DBG*/ $this->dbglog->debug("parse_workbook() function returns 0");
        return 0;
    }

    // ParseFromString & ParseFromFile
    //---------------------------------
    //
    // В:
    //	Содержание строки - содержание Файла
    //	Имя файла строки - Имя файла существующего файла Excel.
    //
    // ИЗ:
    //	0 - Успешно
    //	1 - невозможно открыть файл
    //	2 - Файл, слишком маленький чтобы быть файлом Excel
    //	3 - Ошибка при чтении заголовка
    //	4 - ошибка при чтении файла
    //	5 - Это - не файл Excel или файл, сохраненный в < Excel 5.0
    //	6 - битый файл
    //	7 - данные не найдены
    //	8 - Неподдерживаемая версия файла

    function ParseFromString( $contents ) {
        $this->dbglog->info("ParseFromString() enter.");
        $this->dp = new DataProvider( $contents, DP_STRING_SOURCE );
        return $this->InitParser();
    }

    function ParseFromFile( $filename ) {
        $this->dbglog->info("ParseFromFile() enter.");
        $this->dp = new DataProvider( $filename, DP_FILE_SOURCE );
        return $this->InitParser();
    }

    function InitParser() {
        $this->dbglog->info("InitParser() enter.");
        if( !$this->dp->isValid() ) {
            $this->dbglog->error("InitParser() Failed to open file.");
            $this->dbglog->error("InitParser() function returns 1");
            return 1;
        }
        if( $this->dp->getSize() <= 0x200 ) {
            $this->dbglog->error("InitParser() File too small to be an Excel file.");
            $this->dbglog->error("InitParser() function returns 2");
            return 2;
        }

        $this->max_blocks = $this->dp->getBlocks();

        // чтение заголовка файла
        $hdrbuf = $this->dp->get( 0, 0x200 );
        if( strlen( $hdrbuf ) < 0x200 ) {
            $this->dbglog->error("InitParser() Error reading header.");
            $this->dbglog->error("InitParser() function returns 3");
            return 3;
        }

        // проверка заголовка файла
        $header_sig = array(0xd0,0xcf,0x11,0xe0,0xa1,0xb1,0x1a,0xe1);
        for( $i = 0; $i < count($header_sig); $i++ )
            if( $header_sig[$i] != ord( $hdrbuf[$i] ) ) {
                /*DBG*/    	    $this->dbglog->error("InitParser() function founds invalid header");
                /*DBG*/    	    $this->dbglog->error("InitParser() function returns 5");
                return 5;
            }

        $f_header = new DataProvider( $hdrbuf, DP_STRING_SOURCE );
        unset( $hdrbuf, $header_sig, $i );

        $this->dp->_baseOfs = 0x200;
        $rc = $this->parse_workbook( $f_header, $this->dp );
        unset( $f_header );
        unset( $this->dp, $this->max_blocks, $this->max_sblocks );

        return $rc;
    }
}

?>